import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Random;

import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.client.XmlRpcCommonsTransportFactory;

/**
 * XMLRPCMaster - This class generates matrices, starts timer, sends matrices to
 * slaves, checks/waits until callbackhandler aggregates all the results, ends
 * timer, reports results and terminates.
 * 
 * @author Praneeth Pulusani
 * 
 */
public class XMLRPCMaster {
	/**
	 * 
	 * @param args
	 *            - input arguments to the program <matrixsize>
	 *            <slave0host:port> [slave1host:port]
	 * @param debug
	 *            - If true, prints timing results. When this program is
	 *            executed by itself for a single trial, debug is enabled so
	 *            that the time can be seen. When master driver runs, output
	 *            from here is disabled, as multiple trials are run and master
	 *            driver keeps track of time to get the minimum time.
	 * @return execution time of multiplying two matrices of given size over
	 *         given slaves.
	 */
	public long execute(String[] args, boolean debug) {
		try {
			if(args.length<2){
				System.err.println("Usage: java -jar xmlmaster.jar <matrixsize> <slave0host:port> [slave1host:port...]");
				System.exit(-1);
			}
			// get matrix size from input
			int widthmatrix1 = Integer.parseInt(args[0]);
			int widthmatrix2 = widthmatrix1;// only square matrix size are
											// supported
			int numNodes;

			ArrayList<XmlRpcClient> clients = new ArrayList<XmlRpcClient>();
			// parse all slaves from input
			for (int a = 1; a < args.length; a++) {
				clients.add(createClient(args[a]));
			}
			numNodes = clients.size();
			if (debug)
				System.out.println("Master started with " + numNodes
						+ " nodes to compute results for " + widthmatrix1
						+ " columns");

			// Generate random matrices of given size
			int[] matrix1 = new int[widthmatrix1 * widthmatrix1];
			int[] matrix2 = new int[widthmatrix2 * widthmatrix2];
			Random r = new Random();
			// Matrix 1
			for (int row = 0; row < widthmatrix1; row++) {
				for (int col = 0; col < widthmatrix1; col++) {
					matrix1[row * widthmatrix1 + col] = r.nextInt(10);

				}
			}
			// matrix 2
			for (int row = 0; row < widthmatrix2; row++) {
				for (int col = 0; col < widthmatrix2; col++) {
					matrix2[row * widthmatrix2 + col] = r.nextInt(10);
				}
			}
			// print the matrices if they are small
			if (widthmatrix1 < 6) {
				System.out.println("Matrix 1:");
				printMatrix(widthmatrix1, matrix1);
				System.out.println("Matrix 2:");
				printMatrix(widthmatrix2, matrix1);
			}
			// Put the data in a object to send
			Object[] params = new Object[] { numNodes, widthmatrix1, matrix1,
					widthmatrix2, matrix2 };
			CallBackHandler cbh = new CallBackHandler(widthmatrix1,
					widthmatrix2);
			// Start the timer
			long start = System.currentTimeMillis();
			//Send matrices to all the slaves
			for (int n = 0; n < clients.size(); n++) {
				if (debug) {
					System.out.println("Now sending matrices over to Node " + n);
				}
				//send a reference to call back handler so the slave knows where to send the results
				clients.get(n).executeAsync("MatrixHandler.getMatrixMul",
						params, cbh);
			}
			//continuously check of the callback handler received all the columns of result marix
			while (cbh.getCounter() != widthmatrix2) {
				// need this statement to prevent the program from hanging
				System.out.print("");
			}
			//end timer
			long end = System.currentTimeMillis();
			long execTime = end - start;
			if (debug)
				System.out.println("Execution time is " + (execTime) + "ms");
			//print result if its small
			if (widthmatrix1 < 6) {
				System.out.println("Assembled Result Matrix:");
				printMatrix(widthmatrix1, cbh.getResult());
			}
			return execTime;
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}
		return 0;
	}

	/**
	 * usage: java -jar xmlrpcmaster.jar 1000 localhost:8080 localhost:8081
	 * localhost:8082
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		(new XMLRPCMaster()).execute(args, true);
	}
	/**
	 * CreateClient - helper function to create the xmlrpc client
	 * @param hostport
	 * @return created client
	 * @throws MalformedURLException
	 */
	private static XmlRpcClient createClient(String hostport)
			throws MalformedURLException {
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		config.setServerURL(new URL("http://" + hostport));
		config.setEnabledForExtensions(true);
		config.setGzipRequesting(true);
		XmlRpcClient client = new XmlRpcClient();
		client.setTransportFactory(new XmlRpcCommonsTransportFactory(client));
		client.setConfig(config);
		return client;
	}
	/**
	 * printMatrix - helper method to print the matrix
	 * @param widthmatrix1 width of the matrix (needed so that one dimensional matrix can be printed)
	 * @param matrix - matrix to print 
	 */
	private static void printMatrix(int widthmatrix1, int[] matrix) {
		for (int row = 0; row < widthmatrix1; row++) {
			for (int col = 0; col < widthmatrix1; col++) {
				System.out.print(matrix[row * widthmatrix1 + col] + " ");

			}
			System.out.println();
		}

	}

}